package cn.k7g.alloy.ioc.processor;

import cn.k7g.alloy.autoconfiguration.EnableOptions;
import cn.k7g.alloy.expose.WebExceptionResponseHandler;
import cn.k7g.alloy.utils.AlloyUtils;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MissingRequestValueException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

import javax.security.auth.login.AccountExpiredException;
import javax.validation.ConstraintViolationException;
import java.nio.file.AccessDeniedException;
import java.util.Objects;
import java.util.Properties;
import java.util.stream.Collectors;

/**
 * 增强异常处理器
 * @author victor-wu
 * @date 2021/9/27 下午1:10
 */
@Slf4j
@RestControllerAdvice
@ConditionalOnBean(EnableOptions.EnhanceExceptionMessage.class)
public class EnhanceExceptionResponse {
    public static final String EXCEPTION_MESSAGES_PROPERTIES_BEAN_NAME = "cn.k7g.alloy.ioc.processor.EnhanceExceptionResponse.EXCEPTION_MESSAGES_PROPERTIES";

    @Autowired
    private WebExceptionResponseHandler handler;
    @Autowired
    private MultipartProperties multipartProperties;
    @Autowired
    @Qualifier(EXCEPTION_MESSAGES_PROPERTIES_BEAN_NAME)
    private Properties exceptionMessages;

    private SimpleEvaluationContext context = new SimpleEvaluationContext.Builder(new ReflectivePropertyAccessor(), new MapAccessor()).build();
    private  SpelExpressionParser parser = new SpelExpressionParser();

    @ExceptionHandler(AccessDeniedException.class)
    public Object handleAuthorizationException(AccessDeniedException e) {
        return handler.handle("没有权限，请联系管理员授权", e);
    }

    @ExceptionHandler(AccountExpiredException.class)
    public Object handleAccountExpiredException(AccountExpiredException e) {
        return handler.handle("登录失效了", e);
    }

    @ExceptionHandler(Exception.class)
    public Object handleException(Exception e) {
        String message = exceptionMessages.getProperty(e.getClass().getName());
        if (StringUtils.isNotBlank(message)) {
            if (message.contains("{")) {
                message = (String) parser.parseExpression(message, new TemplateParserContext("{", "}")).getValue(context, e);
            }
            return handler.handle(message, e);
        }
        return handler.handle("系统错误", e);
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public Object handleException(MethodArgumentTypeMismatchException e) {
        String msg = String.format("参数类型错误: [%s] - %s  详细描述： %s", e.getName(),
                e.getParameter().getParameterType().getSimpleName(),
                e.getMessage()) ;
        return handler.handle(msg, e);
    }

    /**
     * query 参数的验证
     * @param e
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Object handleException(ConstraintViolationException e) {
        StringBuilder sb = new StringBuilder();
        e.getConstraintViolations().forEach(o -> {
            String fieldName = AlloyUtils.getFieldAlias(o);
            sb.append(fieldName).append(" ").append(o.getMessage()).append("\n");
        });
        return handler.handle(sb.toString(), e);
    }

    @ExceptionHandler(BindException.class)
    public Object validatedBindException(BindException e) {
        String errorToString = AlloyUtils.getErrorToString(e);
        return handler.handle(errorToString, e);
    }

    @ExceptionHandler(MissingRequestValueException.class)
    public Object missingServletRequestParameterExceptionHandler(MissingRequestValueException e) {
        String message;
        if (e instanceof MissingServletRequestParameterException) {
            MissingServletRequestParameterException e1 = (MissingServletRequestParameterException) e;
            message = String.format("缺少query参数 [%s] ", e1.getParameterName());
        } else {
            message = e.getMessage();
        }
        return handler.handle(message, e);
    }

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public Object maxUploadSizeExceededException(MaxUploadSizeExceededException e) {
        long min = Math.min(multipartProperties.getMaxFileSize().toBytes(), multipartProperties.getMaxRequestSize().toBytes());
        return handler.handle("文件最大支持：" + FileUtils.byteCountToDisplaySize(min), e);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Object invalidFormatException(HttpMessageNotReadableException e) {
        if (e.getCause() instanceof InvalidFormatException) {
            InvalidFormatException cause = (InvalidFormatException) e.getCause();
            String fieldPath = cause.getPath().stream().map(JsonMappingException.Reference::getFieldName).filter(Objects::nonNull).collect(Collectors.joining("."));
            return handler.handle(String.format("%s 是 %s 类型，无法输入值为：%s", fieldPath, cause.getTargetType().getSimpleName(), cause.getValue().toString()), e);
        }
        return handler.handle(e.getMessage(), e);
    }
}
